﻿using System;
using System.Drawing;
using System.Drawing.Imaging;
using Common;
using System.Windows.Forms;

namespace ImageSpaceFiltersPlugin
{
    public class ImageSpaceFilters : IProcessorPlugin
    {
        #region IProcessorPlugin Members

        ImageProcessApp _app;

        public void Load(ImageProcessApp app)
        {
            _app = app;
        }

        public void Unload()
        {

        }

        public void ProcessCommand(string commandName, Document doc)
        {
            switch (commandName)
            {
                case "LaplacianSharpen":
                    {
                        LaplacianSharpen(doc);
                    }
                    break;
                case "GuassianNoise":
                    {
                        GuassianNoise(doc);
                    }
                    break;
                case "Sobel":
                    {
                        Sobel(doc);
                    }
                    break;
                case "ShowHistogram":
                    {
                        ShowHistogram(doc);
                    }
                    break;
                case "HistogramEqualization":
                    {
                        HistogramEqualization(doc);
                    }
                    break;
                case "GrowingRegionSegmentation":
                    {
                        GrowingRegionSegmentation(doc);
                    }
                    break;
                case "ThresholdSegmentation":
                    {
                        ThresholdSegmentation(doc);
                    }
                    break;
                case "Saturate":
                    {
                        Saturate(doc);
                    }
                    break;
                case "SaltAndPepperNoise":
                    {
                        SaltAndPepperNoise(doc);
                    }
                    break;
                case "Invert":
                    Invert(doc);
                    break;
                case "GrayScale":
                    GrayScale(doc);
                    break;
                case "BlackWhite":
                    BlackAndWhite(doc);
                    break;
            }
        }


        private void Invert(Document doc)
        {
            var invertedBitmap = doc.GetBitmap();

            // GDI+ still lies to us - the return format is BGR, NOT RGB.
            var bmData = invertedBitmap.LockBits(new Rectangle(0, 0, invertedBitmap.Width, invertedBitmap.Height),
                                                 ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            var stride = bmData.Stride;
            var scan0 = bmData.Scan0;

            unsafe
            {
                byte* p = (byte*)(void*)scan0;

                var nOffset = stride - invertedBitmap.Width * 3;
                var nWidth = invertedBitmap.Width * 3;

                for (var y = 0; y < invertedBitmap.Height; ++y)
                {
                    for (var x = 0; x < nWidth; ++x)
                    {
                        p[0] = (byte)(255 - p[0]);
                        ++p;
                    }
                    p += nOffset;
                }
            }

            invertedBitmap.UnlockBits(bmData);

            //Ready for preview
            doc.SetBitmapPreview(invertedBitmap);
            //Refresh container to show image with changes
            _app.ImageView.Refresh();

        }
        private void GrayScale(Document doc)
        {
            var original = doc.GetBitmap(); //we will make it to Gray

            //create a blank bitmap the same size as original
            var newBitmap = new Bitmap(original.Width, original.Height);

            //get a graphics object from the new image
            var g = Graphics.FromImage(newBitmap);

            //create the grayscale ColorMatrix
            var colorMatrix = new ColorMatrix(
                new[]
                    {
                        new[] {.3f, .3f, .3f, 0, 0},
                        new[] {.59f, .59f, .59f, 0, 0},
                        new[] {.11f, .11f, .11f, 0, 0},
                        new float[] {0, 0, 0, 1, 0},
                        new float[] {0, 0, 0, 0, 1}
                    });

            //create some image attributes
            var attributes = new ImageAttributes();

            //set the color matrix attribute
            attributes.SetColorMatrix(colorMatrix);

            //draw the original image on the new image
            //using the grayscale color matrix
            g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
               0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
            //dispose graphics
            g.Dispose();

            //Refresh view to show original GrayStyle Image
            doc.SetBitmapPreview(original);

            //Passthrough original bitmap to doc for Undo/Redo operations
            doc.SetBitmap(newBitmap);
            _app.ImageView.Refresh();
        }

        private void BlackAndWhite(Document doc)
        {
            var original = doc.GetBitmap();
            // var rectangle = new Rectangle(0, 0, original.Width, original.Height);
            var rectangle = new Rectangle();
            var blackAndWhite = new Bitmap(original.Width, original.Height);

            // make an exact copy of the bitmap provided
            using (var graphics = Graphics.FromImage(blackAndWhite))
                graphics.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
                    new Rectangle(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);

            // for every pixel in the rectangle region
            for (Int32 xx = rectangle.X; xx < rectangle.X + rectangle.Width && xx < original.Width; xx++)
            {
                for (Int32 yy = rectangle.Y; yy < rectangle.Y + rectangle.Height && yy < original.Height; yy++)
                {
                    // average the red, green and blue of the pixel to get a gray value
                    Color pixel = blackAndWhite.GetPixel(xx, yy);
                    Int32 avg = (pixel.R + pixel.G + pixel.B) / 3;

                    blackAndWhite.SetPixel(xx, yy, Color.FromArgb(0, avg, avg, avg));
                }
            }

            //Refresh view to show original GrayStyle Image
            doc.SetBitmapPreview(blackAndWhite);

            //Passthrough original bitmap to doc for Undo/Redo operations
            //  doc.SetBitmap(newBitmap);
            _app.ImageView.Refresh();
        }
        private void Saturate(Document doc)
        {
            SaturateForm form = new SaturateForm();
            Bitmap grayScale = ImageOperation.ImageSingleTransform(doc.GetBitmap(),
                (ref float r, ref float g, ref float b, int x, int y) =>
                {
                    float val = (r + g + b) * 0.333333f;
                    r = g = b = val;
                });
            Bitmap oriMap = doc.GetBitmap();
            form.ValueChanged += new EventHandler((object sender, EventArgs e) =>
            {
                float value = form.GetValue() / 100.0f;
                Bitmap preview = SaturateBitmap(grayScale, oriMap, value);
                doc.SetBitmapPreview(preview);
                _app.ImageView.Refresh();
            });
            if (form.ShowDialog() == DialogResult.OK)
            {
                doc.SetBitmapPreview(oriMap);
                doc.SetBitmap(SaturateBitmap(grayScale, oriMap, form.GetValue() / 100.0f));

            }
            else
                doc.SetBitmapPreview(oriMap);
            _app.ImageView.Refresh();
        }

        private static Bitmap SaturateBitmap(Bitmap grayScale, Bitmap oriMap, float value)
        {
            Bitmap preview = ImageOperation.MergeBitmap(grayScale, oriMap,
                (Color c1, Color c2, out Color cr) =>
                {
                    cr = Color.FromArgb((Byte)(c1.R * (1.0f - value) + c2.R * value),
                                        (Byte)(c1.G * (1.0f - value) + c2.G * value),
                                        (Byte)(c1.B * (1.0f - value) + c2.B * value));

                });
            return preview;
        }

        private void ThresholdSegmentation(Document doc)
        {
            Bitmap tmpBit = doc.GetBitmap();
            ThresholdSegmentationForm form = new ThresholdSegmentationForm();
            form.ThresholdChanged += new ThresholdSegmentationForm.ThresholdChangedDelegate((int newValue) =>
            {
                doc.SetBitmapPreview(ThresholdSegmentationInternal(form.GetOutputType(), tmpBit, newValue));
                _app.ImageView.Refresh();
            });

            if (form.ShowDialog() == DialogResult.OK)
            {
                doc.SetBitmapPreview(tmpBit);
                doc.SetBitmap(ThresholdSegmentationInternal(form.GetOutputType(), tmpBit, form.GetThreshold()));
            }
            else
                doc.SetBitmapPreview(tmpBit);
            _app.ImageView.Refresh();
        }

        private static Bitmap ThresholdSegmentationInternal(ThresholdSegmentationForm.OutputType output, Bitmap bmp, int newValue)
        {
            Bitmap rs;
            rs = ImageOperation.ImageSingleTransform(bmp,
                (ref float r, ref float g, ref float b, int x, int y) =>
                {
                    float mean = (r + g + b) * 0.33333f;
                    if (mean < newValue)
                    {
                        if (output != ThresholdSegmentationForm.OutputType.BelowThreshold)
                            r = g = b = 0;
                    }
                    else
                    {
                        if (output != ThresholdSegmentationForm.OutputType.AboveThreshold)
                            r = g = b = 255;
                    }
                });
            return rs;
        }

        private void GrowingRegionSegmentation(Document doc)
        {
            RegionGrowForm form = new RegionGrowForm();
            form.Bitmap = doc.GetBitmap();
            if (form.ShowDialog() == DialogResult.OK)
            {
                doc.SetBitmap(ImageOperation.ImageSingleTransform(doc.GetBitmap(), (ref float r, ref float g, ref float b, int x, int y) =>
                    {
                        if (!form.GetMask(x, y))
                        {
                            r = form.GetBackgroundColor().R;
                            g = form.GetBackgroundColor().G;
                            b = form.GetBackgroundColor().B;
                        }
                    }));
                _app.ImageView.Refresh();
            }
        }



        public void RegisterMenu(IMenuRegistry registry)
        {
            registry.RegisterMenuItem("&Adjust", "&Saturate...", "Saturate", Keys.None, this);
            registry.RegisterMenuItem("Anal&ysis", "&Histogram...", "ShowHistogram", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "Laplacian &Sharpen", "LaplacianSharpen", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "Guassian &Noise", "GuassianNoise", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "&Sobel Edge Detection", "Sobel", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "Histogram &Equalization", "HistogramEqualization", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "Growing &Region Segmentation...", "GrowingRegionSegmentation", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "&Threshold Segmentation...", "ThresholdSegmentation", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "&Salt and pepper noise", "SaltAndPepperNoise", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "&Invert", "Invert", Keys.None, this);
            registry.RegisterMenuItem("F&ilter", "&Gray Scale", "GrayScale", Keys.None, this);
            // registry.RegisterMenuItem("F&ilter", "&Black && White", "BlackWhite", Keys.None, this);

        }

        #endregion

        #region Private Methods

        private Bitmap HistorgramEqualizationInternal(Histogram histogram, Bitmap bitmap)
        {
            histogram.ComputeEqualizationMap(bitmap.Width, bitmap.Height);
            return ImageOperation.ImageSingleTransform(bitmap,
                (ref float r, ref float g, ref float b, int x, int y) =>
                {
                    Color c = Color.FromArgb((int)r, (int)g, (int)b);
                    c = histogram.MapColor(c);
                    r = c.R;
                    g = c.G;
                    b = c.B;
                });
        }

        private void HistogramEqualization(Document doc)
        {
            _app.ImageView.Cursor = Cursors.WaitCursor;
            var histogram = new Histogram(doc.GetBitmap());
            doc.SetBitmap(HistorgramEqualizationInternal(histogram, doc.GetBitmap()));
            _app.ImageView.Refresh();
            _app.ImageView.Cursor = Cursors.Default;
        }

        private void ShowHistogram(Document doc)
        {
            _app.ImageView.Cursor = Cursors.WaitCursor;
            Histogram histogram = new Histogram(doc.GetBitmap());
            HistogramForm form = new HistogramForm();
            Bitmap originalBit = doc.GetBitmap();
            form.HistogramEqualize += new HistogramForm.NotifyEvent(() =>
            {
                form.Cursor = Cursors.WaitCursor;
                doc.SetBitmap(HistorgramEqualizationInternal(histogram, originalBit));
                _app.ImageView.Refresh();
                histogram = new Histogram(doc.GetBitmap());
                form.SetHistogram(histogram);
                form.Cursor = Cursors.Default;
            });
            form.SetHistogram(histogram);
            _app.ImageView.Cursor = Cursors.Default;
            if (form.ShowDialog() == DialogResult.Cancel)
            {
                doc.SetBitmap(originalBit);
                _app.ImageView.Refresh();
            }
        }

        private void Sobel(Document doc)
        {
            Bitmap bitX = ImageOperation.ImageMaskTransform(doc.GetBitmap(), new float[]
                {
                    -1, 0, 1,
                    -2, 0, 2,
                    -1, 0 ,1
                }, null);
            Bitmap bitY = ImageOperation.ImageMaskTransform(doc.GetBitmap(), new float[]
                {
                    -1, -2, -1,
                     0,  0,  0,
                     1,  2 , 1
                }, null);
            Bitmap rs = ImageOperation.MergeBitmap(bitX, bitY, (Color c1, Color c2, out Color rc) =>
                {
                    rc = Color.FromArgb((int)ImageOperation.Clampf((float)Math.Sqrt(c1.R * c1.R + c2.R * c2.R), 0, 255),
                                        (int)ImageOperation.Clampf((float)Math.Sqrt(c1.G * c1.G + c2.G * c2.G), 0, 255),
                                        (int)ImageOperation.Clampf((float)Math.Sqrt(c1.B * c1.B + c2.B * c2.B), 0, 255));
                });
            doc.SetBitmap(rs);
            _app.ImageView.Refresh();
        }

        private float NormalNoise()
        {
            float val = 0;

            for (int i = 0; i < 12; i++)
                val += ((float)_rand.NextDouble());
            return val - 6.0f;
        }
        private void GuassianNoise(Document doc)
        {
            Bitmap bitRs = ImageOperation.ImageSingleTransform(doc.GetBitmap(),
                (ref float r, ref float g, ref float b, int x, int y) =>
                {
                    float noise = NormalNoise() * 10.0f;
                    r += noise;
                    g += noise;
                    b += noise;
                });
            doc.SetBitmap(bitRs);
            _app.ImageView.Refresh();
        }

        private void LaplacianSharpen(Document doc)
        {
            float[] mask = 
            {
                0.0f, 1.0f, 0.0f,
                1.0f, 1.0f, 1.0f,
                0.0f, 1.0f, 0.0f
            };
            Bitmap bitRs = ImageOperation.ImageMaskTransform(doc.GetBitmap(), mask, (ref float r, ref float g, ref float b,
                float srcR, float srcG, float srcB) =>
                {
                    r = srcR * 2 - r;
                    g = srcG * 2 - g;
                    b = srcB * 2 - b;
                });
            doc.SetBitmap(bitRs);
            _app.ImageView.Refresh();
        }

        private void SaltAndPepperNoise(Document doc)
        {
            Bitmap bitmap = doc.GetBitmap();
            Bitmap result = new Bitmap(bitmap.Width, bitmap.Height);
            Graphics g = Graphics.FromImage(result);
            g.DrawImage(bitmap, 0, 0);
            Random random = new Random();
            for (int x = 0; x < bitmap.Width; x++)
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    double temp = random.NextDouble();
                    if (temp < 0.05) result.SetPixel(x, y, Color.Black);
                    else if (temp > 0.95) result.SetPixel(x, y, Color.White);
                }
            }
            doc.SetBitmap(result);
            _app.ImageView.ViewMode = ViewMode.BestFit;
        }
        #endregion

        #region Fields member variables
        readonly Random _rand = new Random();
        #endregion
    }
}
